package kunsch.bexp.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.swing.plaf.basic.BasicScrollPaneUI.VSBChangeListener;

import kunsch.bexp.And;
import kunsch.bexp.BooleanFunction;
import kunsch.bexp.Expression;
import kunsch.bexp.False;
import kunsch.bexp.Not;
import kunsch.bexp.Or;
import kunsch.bexp.True;
import kunsch.bexp.Variable;

public class Evaluator
{

	Map<Variable, Boolean> variableValues;

	private Expression booleanExpression;

	public Evaluator()
	{

	}

	public Expression startEvaluation(Expression expression, Map<Variable, Boolean> values)
	{
		this.setBooleanExpression(BExpUtil.copyExpression(expression));
		this.variableValues = values;
		booleanExpression = evaluateExpression(booleanExpression);
		return booleanExpression;
	}

	private Expression evaluateExpression(Expression exp)
	{

		if (exp instanceof Variable)
		{
			return (evaluateVariable(exp));
		}
		else if (exp instanceof And)
		{
			return computeAnd(exp);
		}
		else if (exp instanceof Or)
		{
			return computeOr(exp);
		}
		else if (exp instanceof Not)
		{
			return computeNot(exp);
		}

		return exp;
	}

	private Expression computeNot(Expression exp)
	{
		Not not = (Not) exp;
		evalNot(not);
		if (not.getExp() instanceof True)
			return new False();
		else if (not.getExp() instanceof False)
			return new True();
		else if (not.getExp() instanceof Not)
			return ((Not) not.getExp()).getExp();
		return exp;
	}

	private Expression computeOr(Expression exp)
	{
		List<Expression> list = new ArrayList<Expression>();
		Or or = (Or) exp;
		evalOr(or);

		for (Expression expr : or.getExpressions())
		{
			if (expr instanceof True)
			{
				return new True();
			}
			else
			{
				if (!list.contains(expr))
				{
					list.add(expr);
				}
				if (expr instanceof Variable)
				{
					for (Expression actExp : list)
					{
						if (actExp instanceof Not)
						{
							if (((Not) actExp).getExp().equals(expr))
							{
								list.clear();
								list.add(new True());
							}
						}
					}
				}
				else if (expr instanceof Not)
				{
					for (Expression actExp : list)
					{
						if (actExp instanceof Variable)
						{
							if (((Not) expr).getExp().equals(actExp))
							{
								list.clear();
								list.add(new True());
							}
						}
					}
				}
			}
		}

		for (int i = 0; i < list.size(); i++)
		{
			Expression ex = list.get(i);
			if (ex instanceof False)
				list.remove(i);
		}

		or.setExpressions(list);

		if (or.getExpressions().size() == 0)
			return new False();

		if (or.getExpressions().size() == 1)
			return or.getExpressions().get(0);

		return or;
	}

	private void evalOr(Or or)
	{
		List<Expression> list = new ArrayList<Expression>();
		for (Expression expr : or.getExpressions())
			list.add(evaluateExpression(expr));
		or.setExpressions(list);
	}

	private Expression computeAnd(Expression exp)
	{
		List<Expression> list = new ArrayList<Expression>();
		And and = (And) exp;
		evalAnd(and);

		for (Expression expr : and.getExpressions())
		{
			if (expr instanceof False)
			{
				return new False();
			}
			else
			{
				if (!list.contains(expr))
				{
					list.add(expr);
				}
				if (expr instanceof Variable)
				{
					for (Expression actExp : list)
					{
						if (actExp instanceof Not)
						{
							if (((Not) actExp).getExp().equals(expr))
							{
								list.clear();
								list.add(new False());
							}
						}
					}
				}
				else if (expr instanceof Not)
				{
					for (Expression actExp : list)
					{
						if (actExp instanceof Variable)
						{
							if (((Not) expr).getExp().equals(actExp))
							{
								list.clear();
								list.add(new False());
							}
						}
					}
				}
			}
		}

		for (int i = 0; i < list.size(); i++)
		{
			Expression ex = list.get(i);
			if (ex instanceof True)
				list.remove(i);
		}

		and.setExpressions(list);

		if (and.getExpressions().size() == 0)
			return new True();

		if (and.getExpressions().size() == 1)
			return and.getExpressions().get(0);
		return and;

	}

	private void evalAnd(And and)
	{
		List<Expression> list = new ArrayList<Expression>();
		for (Expression expr : and.getExpressions())
			list.add(evaluateExpression(expr));
		and.setExpressions(list);
	}

	private Expression evaluateVariable(Expression exp)
	{
		Variable left = (Variable) exp;
		if (variableContained(variableValues, left))
		{
			if (getVariable(variableValues, left))
			{
				return new True();
			}
			else
			{
				return new False();
			}
		}
		return left;
	}

	private boolean getVariable(Map<Variable, Boolean> variableValues2, Variable left)
	{
		for (Variable var2 : variableValues2.keySet())
		{
			if (var2.equals(left))
			{
				return variableValues2.get(var2);
			}
		}
		return false;
	}

	private void evalNot(Not not)
	{
		not.setExp(evaluateExpression(not.getExp()));
	}

	private void setBooleanExpression(Expression booleanExpression)
	{
		this.booleanExpression = booleanExpression;
	}

	private boolean variableContained(Map<Variable, Boolean> variableValues, Variable var)
	{
		for (Variable var2 : variableValues.keySet())
		{
			if (var2.equals(var))
				return true;
		}
		return false;
	}

	private boolean variableContained(List<Variable> exprs, Variable var)
	{
		for (Variable var2 : exprs)
		{
			if (var2.equals(var))
				return true;
		}
		return false;
	}

}
